#include <Precomp.h>
#include <dinput.h>
#include <InputListener.h>
#include <InputManager.h>
#include <Windows.h>

CInputManager::CInputManager() : m_pDirectInput(NULL), m_pKeyboardDevice(NULL), m_pMouseDevice(NULL), m_fKeyboardAcquired(false), m_fMouseAcquired(false), m_fUseDIMouseMotion(false)
{

}

bool CInputManager::FInitialize()
{
    HINSTANCE hInst = GetModuleHandle(NULL);
    HRESULT         hr; 
    
    hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION, 
            IID_IDirectInput8, (void**)&m_pDirectInput, NULL); 

    if(FAILED(hr))
    { 
        return false;
    } 

    hr = m_pDirectInput->CreateDevice(GUID_SysKeyboard, &m_pKeyboardDevice, NULL);

    if(FAILED(hr))
    {
        return false;
    }

    HWND hWnd = GetActiveWindow();

    hr = m_pKeyboardDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

    if(FAILED(hr))
    {
        return false;
    }

    hr = m_pKeyboardDevice->SetDataFormat(&c_dfDIKeyboard);

    if(FAILED(hr))
    {
        return false;
    }

    DIPROPDWORD dipdw;

    dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj        = 0;
    dipdw.diph.dwHow        = DIPH_DEVICE;
    dipdw.dwData            = c_uiKeyboardSampleBufferSize; 

    hr = m_pKeyboardDevice->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph );

    if (FAILED(hr))
    {
        return false;
    }

    hr = m_pDirectInput->CreateDevice(GUID_SysMouse, &m_pMouseDevice, NULL);

    if(FAILED(hr))
    {
        return false;
    }

    hr = m_pMouseDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

    if(FAILED(hr))
    {
        return false;
    }

    hr = m_pMouseDevice->SetDataFormat(&c_dfDIMouse2);

    if(FAILED(hr))
    {
        return false;
    }

    // the header
    dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj        = 0;
    dipdw.diph.dwHow        = DIPH_DEVICE;
    dipdw.dwData            = c_uiMouseSampleBufferSize;

    hr = m_pMouseDevice->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if(FAILED(hr))
    {
        return false;
    }   

    m_fKeyboardAcquired = false;
    m_fMouseAcquired = false;

    return true;
}

void CInputManager::Update(IInputListener* pInputListener)
{
    if(FCaptureKeyboard(pInputListener))
    {
        ProcessKeyboardData(pInputListener);
    }

    if(FCaptureMouse(pInputListener))
    {
        ProcessMouseData(pInputListener);
    }
}

bool CInputManager::FCaptureKeyboard(IInputListener* pInputListener)
{
    HRESULT hr = S_OK;

    if(m_pKeyboardDevice == NULL)
    {
        return false;
    }

    if(m_fKeyboardAcquired)
    {
        return true;
    }

    // Get keyboard state
    hr = m_pKeyboardDevice->GetDeviceState(sizeof(m_rgbKeyboardState),(LPVOID)&m_rgbKeyboardState);

    if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
    {
        hr = m_pKeyboardDevice->Acquire();
        if(!FAILED(hr))
        {
            hr = m_pKeyboardDevice->GetDeviceState(sizeof(m_rgbKeyboardState),(LPVOID)&m_rgbKeyboardState);
        }
    }

    if(!FAILED(hr))
    {
        m_fKeyboardAcquired = true;
        pInputListener->OnKeyboardAcquired();
    }


    return !FAILED(hr);
}

bool CInputManager::IsMouseInWindow()
{
    if(m_fUseDIMouseMotion)
    {
        return true;
    }

    POINT pt;
    RECT rect;
    HWND hWnd = GetActiveWindow();

    GetCursorPos(&pt);
    GetWindowRect(hWnd, &rect);

    if(pt.x < rect.left || pt.x > rect.right)
    {
        return false;
    }

    if(pt.y < rect.top || pt.y > rect.bottom)
    {
        return false;
    }

    return true;
}

bool CInputManager::FCaptureMouse(IInputListener* pInputListener)
{
    DIMOUSESTATE2 mouseState;
    HRESULT hr;

    if(m_pMouseDevice == NULL)
    {
        return false;
    }

    if(m_fMouseAcquired)
    {
        if(!IsMouseInWindow())
        {
            m_pMouseDevice->Unacquire();
            return false;
        }

        return true;
    }


    hr = m_pMouseDevice->GetDeviceState(sizeof( DIMOUSESTATE2 ), (LPVOID)&mouseState );

    if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
    {
        hr = m_pMouseDevice->Acquire();
        if(!FAILED(hr))
        {
            hr = m_pMouseDevice->GetDeviceState(sizeof( DIMOUSESTATE2 ), (LPVOID)&mouseState );
        }
    }

    if(FAILED(hr))
    {
        return false;
    }

    POINT pt;
    HWND hWnd = GetActiveWindow();

    GetCursorPos(&pt);
    ScreenToClient(hWnd, &pt);

    m_mouseState.iXPosition = pt.x;
    m_mouseState.iYPosition = pt.y;
    m_mouseState.iZPosition = 0;

    m_mouseState.bButtons = 0;
    for(int i = 0; i < 8; i++)
    {
        if(mouseState.rgbButtons[i] & 0x80)
        {
            m_mouseState.bButtons |= (i << i);
        }
    }

    if(!FAILED(hr))
    {
        m_fMouseAcquired = true;
        pInputListener->OnMouseAcquired();
    }

    return true;
}

void CInputManager::ProcessKeyboardData(IInputListener* pInputListener)
{
    DIDEVICEOBJECTDATA didod[ c_uiKeyboardSampleBufferSize ];  // Receives buffered data 
    DWORD              dwElements;
    HRESULT            hr;

    if(m_pKeyboardDevice == NULL)
    {
        return;
    }

    dwElements = c_uiKeyboardSampleBufferSize;
    hr = m_pKeyboardDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
                                     didod, &dwElements, 0 );
    if(FAILED(hr)) 
    {
        m_fKeyboardAcquired = false;
        return;
    }

    for(unsigned int i = 0; i < dwElements; i++ ) 
    {
        if(didod[i].dwData & 0x80)
        {
            m_rgbKeyboardState[didod[i].dwOfs] = 1;
            pInputListener->KeyPressed((EKeyboardButtonID)didod[i].dwOfs);
        }
        else
        {
            m_rgbKeyboardState[didod[i].dwOfs] = 0;
            pInputListener->KeyReleased((EKeyboardButtonID)didod[i].dwOfs);
        }
    }
}

EMouseButtonID GetButtonNumberFromData(DWORD dwOfs)
{
    if(dwOfs == DIMOFS_BUTTON0)
    {
        return eMouseButtonID_0;
    }

    if(dwOfs == DIMOFS_BUTTON1)
    {
        return eMouseButtonID_1;
    }

    if(dwOfs == DIMOFS_BUTTON2)
    {
        return eMouseButtonID_2;
    }

    return eMouseButtonID_Invalid;
}

bool IsButtonMouseData(DWORD dwOfs)
{
    return GetButtonNumberFromData(dwOfs) != eMouseButtonID_Invalid;
}

bool IsMotionMouseData(DWORD dwOfs)
{
    return !IsButtonMouseData(dwOfs);
}

void CInputManager::ProcessWindowMouseMove(IInputListener* pInputListener)
{
    POINT pt;
    HWND hWnd = GetActiveWindow();

    GetCursorPos(&pt);
    ScreenToClient(hWnd, &pt);

    int dx = pt.x - m_mouseState.iXPosition;
    int dy = pt.y - m_mouseState.iYPosition;

    if(dx != 0 || dy != 0)
    {                               
        pInputListener->MouseMoved(dx, dy, 0);
    }

    m_mouseState.iXPosition = pt.x;
    m_mouseState.iYPosition = pt.y;
}


void CInputManager::ProcessDIMouseMove(IInputListener* pInputListener, int dx, int dy, int dz)
{
    m_mouseState.iXPosition += dx;
    m_mouseState.iYPosition += dy;
    m_mouseState.iZPosition += dz;

    pInputListener->MouseMoved(dx, dy, dz);
}

void CInputManager::ProcessMouseData(IInputListener* pInputListener)
{
    const unsigned int c_uiBufferSize = c_uiMouseSampleBufferSize;
    DIDEVICEOBJECTDATA didod[ c_uiBufferSize ]; 
    DWORD              dwElements;
    HRESULT            hr;

    if(m_pMouseDevice == NULL) 
        return;

    dwElements = c_uiBufferSize;

    hr = m_pMouseDevice->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
                                        didod, &dwElements, 0 );
    if(FAILED(hr)) 
    {
        m_fMouseAcquired = false;
        return;
    }

    unsigned int dx = 0, dy = 0, dz = 0;

    bool fMouseMovePending = false;

    for(unsigned int i = 0; i < dwElements; i++ ) 
    {
        DWORD dwOfs = didod[i].dwOfs;

        if(IsButtonMouseData(dwOfs))
        {
            if(fMouseMovePending)
            {
                ProcessDIMouseMove(pInputListener, dx, dy, dz);
                dx = dy = dz = 0;
                fMouseMovePending = false;
            }

            EMouseButtonID eMouseButtonID = GetButtonNumberFromData(dwOfs);

            if(didod[i].dwData & 0x80)
            {
                m_mouseState.bButtons |= (i << eMouseButtonID);
                pInputListener->MousePressed(eMouseButtonID);
            }
            else
            {
                m_mouseState.bButtons &= ~(i << eMouseButtonID);
                pInputListener->MouseReleased(eMouseButtonID);
            }
        }
        else if(IsMotionMouseData(dwOfs))
        {
            if(dwOfs == DIMOFS_X && m_fUseDIMouseMotion)
            {
                fMouseMovePending = true;
                dx += didod[i].dwData;
            }
            else if(dwOfs == DIMOFS_Y && m_fUseDIMouseMotion)
            {
                fMouseMovePending = true;
                dy += didod[i].dwData;
            }
            else if(dwOfs == DIMOFS_Z)
            {
                fMouseMovePending = true;
                dz += didod[i].dwData;
            }
        }
    }

    if (fMouseMovePending)
    {
        ProcessDIMouseMove(pInputListener, dx, dy, dz);
        dx = dy = dz = 0;
        fMouseMovePending = false;
    }

    if(!m_fUseDIMouseMotion)
    {
        ProcessWindowMouseMove(pInputListener);
    }

    return;
}


void CInputManager::Cleanup()
{
    if(m_pMouseDevice != NULL)
    {
        m_pMouseDevice->Release();
        m_pMouseDevice = NULL;
    }

    if(m_pKeyboardDevice != NULL)
    {
        m_pKeyboardDevice->Release();
        m_pKeyboardDevice = NULL;
    }


    if(m_pDirectInput != NULL)
    {
        m_pDirectInput->Release();
        m_pDirectInput = NULL;
    }

    m_fKeyboardAcquired = m_fMouseAcquired = false;
}                                   
                                            
int CInputManager::GetMouseX() { return m_mouseState.iXPosition; }
int CInputManager::GetMouseY() { return m_mouseState.iYPosition; }

bool CInputManager::IsKeyDown(EKeyboardButtonID eButtonID)
{
    return !!m_rgbKeyboardState[eButtonID];
}

